var TEST_HOME = '/home/cyu/workspace/microformats_tests';

var dataTypes = [
    {name: "hcard"    , ext: "vcf", exportMethod: "toVCard"    , outputName: "VCARD"},
    {name: "hcalendar", ext: "ics", exportMethod: "toICalendar", outputName: "VCALENDAR"}
];

var typeIndex = 0;
var testIndex = 0;
var totalTestCount = 0;

var expectedOutput = null;
var actualOutput   = null;

var testListBoxes = [];

function runNextText() {
  if (testIndex >= testListBoxes[typeIndex].getTestCount()) {
    typeIndex++;
    testIndex = 0;
  }

  if (typeIndex >= testListBoxes.length) {
    alert(totalTestCount + " tests ran");
    return;
  }

  var testList = testListBoxes[typeIndex];
  var test = testList.getTest(testIndex);
  
  var doc = toHTMLDoc(test.getContents());

  // load the microformats
  var parsedObjects = [];
  TailsParser.parse(doc, function(format, node)
      { parsedObjects.push(format.parseObject(node)); });

  // get the expected output  
  var expected = test.getExpectedOutput();
  if (expected != null) {
    expected = expected
        .replace(/\$PRODID\$/g, TailsConfig.prodId)
        .replace(/\$SOURCE\$/g, doc.location.href);
      
    // only use the objects of the current test type.  
    var actual = test.buildOutput(parsedObjects);
    
    test.setResults(expected, actual,
        new Validator(dataTypes[typeIndex], expected, actual).valid);
  } else {
    test.setFailure("EXPECTED FILE NOT FOUND");
  }
  
  totalTestCount++;
  testIndex++;
  runNextText();
}

function toHTMLDoc(contents) {
  var doc = TailsNS.parseDOM(contents, "text/xml");

  var title = doc.getElementsByTagName("title");
  doc.title = title.length > 0 ? title[0].firstChild.data : null;
  doc.location = new Object();
  //doc.location.href = url.spec;
  doc.location.href = "http://codeeg.com";

  // for some reason the native version of this method doesn't work.
  doc.getElementById = function(id) {
    var proc = function(n) {
      var children = n.childNodes;
      for (var i=0; i<children.length; i++) {
        if (children[i].nodeName[0] != "#"[0]) {
          if (children[i].getAttribute('id') == id) {
            return children[i];
          } else {
            var r = proc(children[i]);
            if (r) return r;
          }
        }
      }
    }
    return proc(this.documentElement);
  }

  return doc;
}

function main() {
  // set components
  expectedOutput = new ResultBox(document.getElementById("expected-output"));
  actualOutput   = new ResultBox(document.getElementById("actual-output"));
  
  for (var i=0; i<dataTypes.length; i++)
    testListBoxes.push(new TestListBox(dataTypes[i]));

  setTimeout(runNextText, 1000);  
}

function testSelected(listbox) {
  expectedOutput.clear();
  actualOutput.clear();
  
  var validator = new Validator(listbox.__testListBox.dataType,
      listbox.selectedItem.__expected, listbox.selectedItem.__actual);
  
  var lines = validator.getLines();
  for (var i=0; i<lines.length; i++) {
     expectedOutput.addLine(lines[i].expected, lines[i].equals);
     actualOutput.addLine(lines[i].actual, lines[i].equals);
  }
}

function Validator(dataType, expected, actual) {
  this.valid = true;

  var expectedLines = expected.split(/[\r\n]+/);
  var actualLines   = actual.split(/[\r\n]+/);
  
  while (expectedLines[0] == "")
    expectedLines.shift();
  while (actualLines[0] == "")
    actualLines.shift();
  
  while (expectedLines[expectedLines.length-1] == "")
    expectedLines.pop();
  while (actualLines[actualLines.length-1] == "")
    actualLines.pop();
  
  //alert(expectedLines.length + ":" + actualLines.length);
  
  this.getLines = function() {
    var expGroups = this.buildGroups(dataType.outputName, expectedLines);
    var actGroups = this.buildGroups(dataType.outputName, actualLines);

    var lines = [];
    var i = 0;
    while (i<expGroups.length) {
      if (i < actGroups.length) {
        this.matchLines(expGroups[i], actGroups[i], lines);
      } else
        this.dumpLines(expGroups[i], 'expected', lines);
      i++;
    }
    
    while (i < actGroups.length) {
      this.dumpLines(actGroups[i], 'actual', lines);
      i++;
    }
    
    return lines;
  }

  this.dumpLines = function(group, lineType, lines) {
    var groupLines = group.getLines();
    for (var i=0; i<groupLines.length; i++) {
      var line = { expected: "<blank>", actual: "<blank>", equals: false };
      line[lineType] = groupLines[i];
      lines.push(line);
    }
  }

  this.matchLines = function(expGroup, actGroup, lines) {
    if (expGroup.validBegin && actGroup.validBegin) {
      lines.push({ expected: expGroup.beginLine, actual: actGroup.beginLine, equals: true });
    } else if (expGroup.validBegin) {
      lines.push({ expected: expGroup.beginLine, actual: "<blank>", equals: false });
    } else {
      lines.push({ expected: "<blank>", actual: actGroup.beginLine, equals: false });
    }
    
    for (var i=0; i<expGroup.lines.length; i++) {
      var expLine = expGroup.lines[i];
      var line = { expected: expLine, actual: "<blank>", equals: false };
      
      var actLine = actGroup.popClosestMatch(expGroup.lines[i]);
      if (actLine) {
        line.actual = actLine;
        if (expLine == actLine)
          line.equals = true;
        else if (expLine.replace(/\s/g, "") == actLine.replace(/\s/g, ""))
          line.actual = actLine.replace(/ /g, "<space>");
      }
      lines.push(line);
    }
    
    var remaining = actGroup.lines;
    for (var i=0; i<remaining.length; i++)
      lines.push({ expected: "<blank>", actual: remaining[i], equals: false });
    
    if (expGroup.validEnd && actGroup.validEnd) {
      lines.push({ expected: expGroup.endLine, actual: actGroup.endLine, equals: true });
    } else if (expGroup.validEnd) {
      lines.push({ expected: expGroup.endLine, actual: "<blank>", equals: false });
    } else {
      lines.push({ expected: "<blank>", actual: actGroup.endLine, equals: false });
    }
    
    return lines;
  }
  
  this.buildGroups = function(typeName, lines) {
    var groups = [];
    var group = [];
    for (var i=0; i<lines.length; i++) {
      group.push(lines[i]);
      if (lines[i] == ("END:"+typeName)) {
        groups.push(new ResultGroup(typeName, group));
        group = [];
      }
    }
    if (group != null && group.length != 0)
      groups.push(new ResultGroup(typeName, group));
    return groups;
  }

  var l = this.getLines();
  for (var i=0; i<l.length; i++) {
    if (!l[i].equals) {
      this.valid = false;
      break;
    }
  }
  
  return this;
}

// a result group
function ResultGroup(typeName, lines) {
  this.typeName = typeName;
  this.validBegin = (lines[0] == ("BEGIN:" + typeName));
  this.validEnd   = (lines[lines.length - 1] == ("END:" + typeName));
  
  this.lines = lines.slice();
  if (this.validBegin) {
    this.beginLine = lines[0];
    this.lines.splice(0,1);
  }
  if (this.validEnd) {
    this.endLine = lines[lines.length - 1];
    this.lines.splice(this.lines.length-1, 1);
  }
  
  this.getName = function(l) {
    if (!l.match(/^(.*?)(;|:)/))
      alert("error: " + l);
    return RegExp.$1;
  }
  
  this.index = {};
  for (var i=0; i<this.lines.length; i++) {
    var name = this.getName(this.lines[i]);
    if (!this.index[name]) this.index[name] = [];
    this.index[name].push(this.lines[i]);
  }

  this.getLines = function() {
    var r = this.lines.slice();
    if (this.validBegin) r.unshift("BEGIN:"+typeName);
    if (this.validEnd) r.push("END:"+typeName);
    return r;
  }

  this.popClosestMatch = function(line) {
    var matchName = this.getName(line);
    var arr = this.index[matchName];
    if (!arr || arr.length == 0) return null;
    
    var r = arr.shift();
    for (var i=0; i<this.lines.length; i++) {
      if (this.lines[i] == r) {
        this.lines.splice(i,1);
      }
    }
    return r;
  }
  return this;
}

// the test list box
function TestListBox(dataType) {
  this.dataType = dataType;
  
  // get the test directory
  var dir = Components.classes["@mozilla.org/file/local;1"]
      .createInstance(Components.interfaces.nsILocalFile);
  dir.initWithPath(TEST_HOME + "/" + dataType.name);

  this.listBox = document.getElementById(dataType.name + "-tests");
  this.listBox.__testListBox = this;

  // add tests to list box
  var entries = dir.directoryEntries;
  while (entries.hasMoreElements()) {
    var currFile = entries.getNext();
    currFile.QueryInterface(Components.interfaces.nsILocalFile);
    
    if (currFile.leafName.match(/.*?\.html$/)) {
      var item = document.createElement("listitem");
      
      var cell1 = document.createElement("listcell");
      cell1.setAttribute("label", currFile.leafName);
      item.appendChild(cell1);
      
      var cell2 = document.createElement("listcell");
      cell2.setAttribute("label", "Ready");
      item.appendChild(cell2);
      
      this.listBox.appendChild(item);
    }
  }
  
  this.getTestCount = function() {
    return this.listBox.getElementsByTagName("listitem").length;
  }
  
  this.getTest = function(index) {
    var listItems = this.listBox.getElementsByTagName("listitem");
    return new Test(this.dataType, listItems[index]);
  }

  return this;
}

function Test(dataType, listItem) {
  this.dataType = dataType;
  
  this.listItem  = listItem;
  this.cells = listItem.getElementsByTagName("listcell");
  this.inputFile = this.cells[0].getAttribute("label");
  
  this.getContents = function() {
    var url = TailsNS.getURI(TailsNS.getLocalFile(
        TEST_HOME + "/" + this.dataType.name, this.inputFile));
    return TailsNS.getContents(url);
  }
  
  this.getExpectedOutput = function() {
    var f = TailsNS.getLocalFile(TEST_HOME + "/" + this.dataType.name,
        this.inputFile.substring(0, this.inputFile.indexOf('.')) + "." + this.dataType.ext);
    return (f.exists())? TailsNS.getContents(TailsNS.getURI(f)) : null;
  }

  this.setResults = function(expected, actual, passed) {
    this.listItem.__expected = expected;
    this.listItem.__actual   = actual;
    
    if (passed) {
      this.cells[1].setAttribute("label", "OK");
      this.cells[1].setAttribute("style", "color: green;");
    } else {
      this.setFailure("Failed");
    }
  }
  
  this.setFailure = function(msg) {
    this.cells[1].setAttribute("label", msg);
    this.cells[1].setAttribute("style", "color: red;");
  }
  
  this.buildOutput = function(objects) {
    // have to do something special for hcalendar results
    if (objects.length > 0 && objects[0].__name == 'hcalendar') {
      return TailsConfig.getFormat('hcalendar').toICalendar(objects);
      
    // for everything else
    } else {
      var s = "";
      for (var i=0; i<objects.length; i++) {
        if (s.length > 0) s += '\n';
        if (objects[i].__name == this.dataType.name)
          s += objects[i][this.dataType.exportMethod]();
      }
      return s;
    }
  }

  return this;
}

// the test output boxes
function ResultBox(comp) {
  this.comp = comp;
  this.clear = function() {
    while (comp.getRowCount() != 0)
      comp.removeItemAt(0);
  }
  this.addLine = function(line, equals) {
    var item = document.createElement("listitem");
    item.setAttribute("label", line);
    if (!equals) item.setAttribute("style", "color: red;");
    this.comp.appendChild(item);
  }
  return this;
}
